package org.xx.armory.commons;

import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.Charset;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.YearMonth;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TimeZone;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.regex.Pattern;
import java.util.stream.Collector;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import static java.util.Arrays.stream;
import static java.util.Base64.getEncoder;
import static java.util.Collections.emptyList;
import static java.util.regex.Pattern.compile;
import static org.apache.commons.lang3.StringUtils.substring;
import static org.apache.commons.lang3.StringUtils.trimToEmpty;
import static org.xx.armory.commons.TextUtils.replaceByRegex;
import static org.xx.armory.commons.Validators.rejectIf;
import static org.xx.armory.commons.Validators.rejectIfNegative;
import static org.xx.armory.commons.Validators.rejectIfNull;

/**
 * 常用功能扩展工具类。
 *
 * @author Haart
 */
public final class SysUtils {
    private static final int BUFFER_SIZE = 1024 * 4;
    private static final Pattern SPLIT_PATTERN = compile(",", Pattern.LITERAL);
    private static final Pattern HASH_NORM_FILTER = compile("[/*%=+-]");
    private final static Pattern ENV_VARIABLE_NAME = compile("\\$\\{([a-z0-9_.-]+)}", Pattern.DOTALL | Pattern.CASE_INSENSITIVE);
    private static final long HOURS_PER_DAY = 24L;
    private static final long MINUTES_PER_HOUR = 60L;
    private static final long SECONDS_PER_MINUTE = 60L;
    private static final long MILLISECONDS_PER_SECOND = 1000L;
    private static final long MILLISECONDS_PER_DAY = MILLISECONDS_PER_SECOND * SECONDS_PER_MINUTE * MINUTES_PER_HOUR * HOURS_PER_DAY;

    private SysUtils() {
        throw new AssertionError();
    }

    public static Short copyShort(
            Number src
    ) {
        return src != null ? src.shortValue() : null;
    }

    public static Byte copyByte(
            Number src
    ) {
        return src != null ? src.byteValue() : null;
    }

    public static Integer copyInteger(
            Number src
    ) {
        return src != null ? src.intValue() : null;
    }

    public static Long copyLong(
            Number src
    ) {
        return src != null ? src.longValue() : null;
    }

    public static Float copyFloat(
            Number src
    ) {
        return src != null ? src.floatValue() : null;
    }

    public static Double copyDouble(
            Number src
    ) {
        return src != null ? src.doubleValue() : null;
    }

    /**
     * 复制日期类型。
     * <p>如果参数{@code src}是{@code null}则返回{@code null}，否则构造一个与{@code src}表示的日期相同的新对象并返回。</p>
     *
     * @param src
     *         待复制的日期对象。
     * @return 复制的结果。
     */
    public static Date copyDate(
            Date src
    ) {
        return src != null ? new Date(src.getTime()) : null;
    }

    /**
     * 复制日期类型。
     * <p>如果参数 {@code src} 是 {@code null} 则返回 {@code null}，否则构造一个与{@code src}表示的日期相同的新 {@link java.sql.Timestamp} 对象并返回。</p>
     *
     * @param src
     *         待复制的日期对象。
     * @return 复制的结果。
     */
    public static java.sql.Timestamp copyTimestamp(
            Date src
    ) {
        return src != null ? new java.sql.Timestamp(src.getTime()) : null;
    }

    /**
     * 复制列表。
     *
     * @param oc
     *         原始集合。
     * @param <E>
     *         集合元素类型。
     * @return 复制的结果。
     */
    public static <E> List<E> copyList(
            Collection<? extends E> oc
    ) {
        if (oc == null) {
            return null;
        }

        return new ArrayList<>(oc) {
            @Override
            public boolean equals(
                    Object o
            ) {
                if (o == this) {
                    return true;
                }

                if (o instanceof final List<?> oo) {
                    if (oo.size() != this.size()) {
                        return false;
                    }
                    boolean r = true;
                    for (int i = 0; i < this.size(); ++i) {
                        r = r && Objects.equals(oo.get(i), this.get(i));
                    }
                    return r;
                }

                return false;
            }
        };
    }

    /**
     * 计算两个日期之间的差值，按照指定的时区忽略时分秒部分。
     *
     * @param later
     *         较晚的日期。
     * @param earlier
     *         较早的日期。
     * @param timeZone
     *         指定的时区。
     * @return 两个日期之间的差值，以日为单位，如果参数{@code later > earlier} ，那么返回正值，如果参数{@code later < earlier}，那么返回负值，如果参数
     * {@code later == earlier}，那么返回{@code 0}。
     */
    public static double diffDays(
            Date later,
            Date earlier,
            TimeZone timeZone
    ) {
        rejectIfNull(later, "later");
        rejectIfNull(earlier, "earlier");
        rejectIfNull(timeZone, "timeZone");

        final var oft1 = timeZone.getOffset(later.getTime());
        final var oft2 = timeZone.getOffset(earlier.getTime());

        return (double) (later.getTime() - earlier.getTime() + oft1 - oft2) / MILLISECONDS_PER_DAY;
    }

    /**
     * 计算两个本地日期之间的差值，忽略2个日期的时分秒部分。
     *
     * @param later
     *         较晚的日期。
     * @param earlier
     *         较早的日期。
     * @return 两个日期之间的差值，以日为单位，如果参数{@code later > earlier} ，那么返回正值，如果参数{@code later < earlier}，那么返回负值，如果参数
     * {@code later == earlier}，那么返回{@code 0}。
     */
    public static double diffDays2(
            Date later,
            Date earlier
    ) {
        return diffDays(later, earlier, TimeZone.getDefault());
    }

    /**
     * 计算两个日期之间的差值，按照指定的时区忽略时分秒部分。
     *
     * @param later
     *         较晚的日期。
     * @param earlier
     *         较早的日期。
     * @param timeZone
     *         指定的时区。
     * @return 两个日期之间的差值，以月为单位，如果参数{@code later > earlier} ，那么返回正值，如果参数{@code later < earlier}，那么返回负值，如果参数
     * {@code later == earlier}，那么返回{@code 0}。
     */
    public static int diffMonths(
            Date later,
            Date earlier,
            TimeZone timeZone
    ) {
        rejectIfNull(later, "later");
        rejectIfNull(earlier, "earlier");
        rejectIfNull(timeZone, "timeZone");

        final var cal = Calendar.getInstance(timeZone);

        cal.setTime(later);
        var year1 = cal.get(Calendar.YEAR);
        var month1 = cal.get(Calendar.MONTH);

        cal.setTime(earlier);
        var year2 = cal.get(Calendar.YEAR);
        var month2 = cal.get(Calendar.MONTH);

        return (year1 - year2) * 12 + month1 - month2;
    }

    /**
     * 计算两个本地日期之间的差值，忽略2个日期的时分秒部分。
     *
     * @param later
     *         较晚的日期。
     * @param earlier
     *         较早的日期。
     * @return 两个日期之间的差值，以月为单位，如果参数{@code later > earlier} ，那么返回正值，如果参数{@code later < earlier}，那么返回负值，如果参数表示同一个月那么返回{@code 0}。
     */
    public static int diffMonths2(
            Date later,
            Date earlier
    ) {
        return diffMonths(later, earlier, TimeZone.getDefault());
    }

    /**
     * 根据指定日期和持续天数计算出日期范围。
     *
     * <p>参数{@code date}会按照指定的时区被截取到该日的零点零分。作为第一个时间点。然后加上指定的天数，作为第二个时间点。然后以较早的日期作为起始时间点，较晚的作为结束时间点，创建日期范围。</p>
     *
     * @param date
     *         指定的日期。
     * @param days
     *         从指定日期开始的持续天数。
     * @param timeZone
     *         时区，如果此参数是 {@code null} 则使用系统默认时区。
     * @return 日期范围。
     * @throws IllegalArgumentException
     *         如果参数 {@code date} 是 {@code null}。
     */
    public static DateRange daysRange(
            Date date,
            int days,
            TimeZone timeZone
    ) {
        rejectIfNull(date, "date");

        if (timeZone == null) {
            timeZone = TimeZone.getDefault();
        }

        var c = Calendar.getInstance(timeZone);
        c.setTime(date);

        c = DateUtils.truncate(c, Calendar.DATE);
        final var start = c.getTime();
        c.add(Calendar.DATE, days);
        final var end = c.getTime();

        return days >= 0 ? new DateRange(start, end) : new DateRange(end, start);
    }

    /**
     * 连接字符串
     *
     * <p>
     * 将任意个字符串参数按照传入的顺序首尾相连
     * </p>
     * <p>
     * 如果传入的参数为{@code null}或者长度为{@code 0}, 那么返回值为空字符串。
     * </p>
     * <p>
     * <strong>如果某个字符串参数是{@code null}, 那么该字符串会被跳过。</strong>
     * </p>
     *
     * <pre>
     * 示例：
     *
     * concat() == "";
     * concat("j", "um", "p") == "jump";
     * concat("a", null, "d") == "ad";
     * </pre>
     *
     * @param first
     *         用于连接的第一个字符串
     * @param others
     *         用于连接的其它字符串。
     * @return 将字符串参数按照传入的顺序首尾连接得到的字符串。
     * @see java.lang.StringBuilder#append(String)
     */
    public static String concat(
            String first,
            String... others
    ) {
        if (others == null) {
            return first != null ? first : "";
        }

        var l = first != null ? first.length() : 0;
        for (var s : others) {
            l += s.length();
        }

        final var sb = new StringBuilder(l);
        if (first != null) {
            sb.append(first);
        }
        for (final var s : others) {
            if (s != null) {
                sb.append(s);
            }
        }

        return sb.toString();
    }

    /**
     * 使用逗号分割字符串，去掉空白部分。
     *
     * <p>
     * 示例：
     * </p>
     *
     * <pre>
     * split(null) = []
     * split("") = []
     * split("  ") = []
     * split("1") = ["1"]
     * split("1,abc,6") = ["1", "abc", "6"]
     * split("1,abc,6,") = ["1", "abc", "6"]
     * split(",abc  def, km ,  ,6,") = ["abc  def", "km", "6"]
     * </pre>
     *
     * @param s
     *         被分割的字符串。
     * @return 分割的结果。
     */
    public static Collection<String> split(
            String s
    ) {
        if (s == null) {
            return emptyList();
        }

        final var ss = SPLIT_PATTERN.split(s);
        final var ret = new ArrayList<String>(ss.length);
        for (final var term : ss) {
            final String term1 = term.trim();
            if (!term1.isEmpty()) {
                ret.add(term1);
            }
        }
        return ret;
    }

    /**
     * 合并两个容器中的数据。
     *
     * <p>
     * 如果容器{@code c1}为{@code null}, 则只返回容器{@code c2}的元素。如果容器{@code c2}为{@code null}, 则只返回容器
     * {@code c1} 的元素。如果两个容器都是{@code null}, 则返回的结果不包含任何元素。
     * </p>
     *
     * @param <V>
     *         容器的元素类型。
     * @param c1
     *         需要被合并的容器。
     * @param c2
     *         需要被合并的容器。
     * @return 包含了容器{@code c1}和{@code c2}中所有的数据的集合。
     * <ul>
     * <li>相同的元素在返回的集合中只会包含一次。</li>
     * <li>不保证元素被添加的顺序和原容器相同。</li>
     * </ul>
     * @see java.util.Set#addAll(Collection)
     */
    public static <V> Set<? super V> merge(
            Collection<? extends V> c1,
            Collection<? extends V> c2
    ) {
        final var ret = new LinkedHashSet<V>();

        if (c1 != null) {
            ret.addAll(c1);
        }
        if (c2 != null) {
            ret.addAll(c2);
        }

        return ret;
    }

    /**
     * 创建一系列目录，包括指定的目录和它的所有上级目录。
     *
     * @param dir
     *         待创建的目录。
     * @return 如果指定目录创建成功或者已存在则返回 {@code true}，否则返回{@code false}。
     * @throws IllegalArgumentException
     *         如果参数{@code dir}是{@code null}。
     */
    public static boolean mkdirs(
            File dir
    ) {
        rejectIfNull(dir, "dir");

        return dir.mkdirs() || dir.exists() && dir.isDirectory();
    }

    /**
     * 按照给定的数值，计算对应的散列字节数组。
     *
     * <p>
     * 为了保证散列字节数组的有效性，用于计算的原始值应当尽量保证唯一性。
     * </p>
     *
     * @param values
     *         用于计算散列字节数组的原始值，可以是任意类型的值，包括 {@code null} 。
     * @return 根据原始值计算出来的散列字节数组，该数组的长度不小于 {@code 20}。
     * @throws java.lang.NullPointerException
     *         如果参数{@code srcObjects}的值是{@code null}。
     * @see SecurityUtils#sha1(int)
     */
    public static byte[] uniqueHashBytes(
            Object... values
    ) {
        return SecurityUtils.sha1(Objects.hash(values));
    }

    /**
     * 按照给定的数值，计算对应的散列字符串，该字符串的长度固定为6。
     *
     * <p>
     * 为了保证散列字节串的有效性，用于计算的原始值应当尽量保证唯一性。
     * </p>
     * <p>
     * 散列字符串只包含数字、小写英文字母、大写英文字母和连字符(-)。
     * </p>
     *
     * @param values
     *         用于计算散列字符串的原始值，可以是任意类型的值，包括 {@code null} 。
     * @return 根据原始值计算出来的字符串，该字符串的长度固定为{@code 6}。
     * @throws java.lang.NullPointerException
     *         如果参数<code>srcObjects</code>的值是 {@code null} 。
     * @see #uniqueHashBytes(Object[])
     */
    public static String uniqueHash(
            Object[] values
    ) {
        return HASH_NORM_FILTER
                .matcher(getEncoder().encodeToString(uniqueHashBytes(values)).substring(2, 8))
                .replaceAll("-");
    }

    /**
     * 按照给定的数值，计算对应的散列字符串，该字符串的长度固定为6。
     *
     * <p>
     * 为了保证散列字节串的有效性，用于计算的原始值应当尽量保证唯一性。
     * </p>
     * <p>
     * 散列字符串只包含数字、小写英文字母、大写英文字母和连字符(-)。
     * </p>
     *
     * @param values
     *         用于计算散列字符串的原始值，可以是任意类型的值，包括 {@code null} 。
     * @return 根据原始值计算出来的字符串，该字符串的长度固定为{@code 8}。
     * @throws java.lang.NullPointerException
     *         如果参数<code>srcObjects</code>的值是 {@code null} 。
     * @see #uniqueHashBytes(Object[])
     */
    public static String uniqueHash8(
            Object[] values
    ) {
        return HASH_NORM_FILTER
                .matcher(getEncoder().encodeToString(uniqueHashBytes(values)).substring(2, 10))
                .replaceAll("-");
    }

    /**
     * 判断指定的数组是否是{@code null}或者是空数组。
     *
     * @param array
     *         判断的数组。
     * @return 如果该数组是 {@code null}或者是空数组(长度为0)则返回{@code true}，否则返回{@code false}。
     */
    public static boolean notEmpty(
            Object array
    ) {
        return array == null || Array.getLength(array) != 0;
    }

    /**
     * 判断给定的{@code java.math.BigDecimal}对象是否是0。
     *
     * @param d
     *         需判断的{@code java.math.BigDecimal}对象
     * @return 如果 d 的值是0，那么返回{@code true}，否则返回{@code false}。
     * @throws java.lang.NullPointerException
     *         如果参数 {@code d == null}。
     */
    public static boolean isZero(
            java.math.BigDecimal d
    ) {
        rejectIfNull(d, "d");

        return d.compareTo(java.math.BigDecimal.ZERO) == 0;
    }

    /**
     * 将指定的列表的长度调整到期望的大小。
     *
     * @param list
     *         需调整长度的列表。
     * @param newSize
     *         新的长度。
     * @throws IllegalArgumentException
     *         如果参数{@code list}是{@code null}，或者采纳数{@code newSize}小于{@code 0}。
     */
    public static void resize(
            List<?> list,
            int newSize
    ) {
        rejectIfNull(list, "list");
        rejectIfNegative(newSize, "newSize");

        if (newSize == 0) {
            list.clear();
        }

        // 如果列表的长度不是期望的长度，那么需要调整列表。
        if (newSize != list.size()) {
            if (newSize > list.size()) {
                // 需要扩充到指定的长度。
                int i = list.size();
                while (i < newSize) {
                    list.add(null);
                    ++i;
                }
            } else {
                // 需要缩减到指定的长度。
                while (list.size() > newSize) {
                    list.remove(list.size() - 1);
                }
            }
        }
    }

    /**
     * 获取指定数组的指定下标的元素，如果超过数组的上限则返回默认值。
     *
     * @param array
     *         数组。
     * @param index
     *         下标。
     * @param defaultValue
     *         默认值。
     * @param <T>
     *         数组的元素类型。
     * @return 指定数组的指定下标的元素。
     * @throws IllegalArgumentException
     *         如果参数{@code array}是{@code null}。
     * @throws ArrayIndexOutOfBoundsException
     *         如果参数{@code index}小于{@code 0}。
     */
    public static <T> T getOrDefault(
            T[] array,
            int index,
            T defaultValue
    ) {
        rejectIfNull(array, "array");

        if (index < 0) {
            throw new ArrayIndexOutOfBoundsException(index);
        }

        if (index >= array.length) {
            return defaultValue;
        } else {
            return array[index];
        }
    }

    /**
     * 获取指定数组的指定下标的元素，如果超过数组的上限则返回默认值。
     *
     * @param array
     *         数组。
     * @param index
     *         下标。
     * @param defaultValue
     *         默认值。
     * @param <T>
     *         数组的元素类型。
     * @return 指定数组的指定下标的元素。
     * @throws IllegalArgumentException
     *         如果参数{@code array}是{@code null}。
     * @throws ArrayIndexOutOfBoundsException
     *         如果参数{@code index}小于{@code 0}。
     */
    public static <T> T getOrDefault(
            Collection<T> array,
            int index,
            T defaultValue
    ) {
        rejectIfNull(array, "array");

        if (index < 0) {
            throw new ArrayIndexOutOfBoundsException(index);
        }

        if (index >= array.size()) {
            return defaultValue;
        } else {
            var i = -1;
            var iterator = array.iterator();
            T result = defaultValue;
            while (i < index) {
                result = iterator.next();
                ++i;
            }
            return result;
        }
    }

    public static <E> void forEachGroup(
            Stream<E> stream,
            int groupSize,
            GroupConsumer<E> gc
    ) {
        final var group = new ArrayList<E>(groupSize);
        var index = new int[]{0, 0};

        stream.forEach(e -> {
            if (group.size() >= groupSize) {
                gc.accept(group.stream(), index[1]);
                group.clear();
                group.add(e);
                index[1] = index[0]++;
            } else {
                group.add(e);
                ++index[0];
            }
        });

        if (!group.isEmpty()) {
            gc.accept(group.stream(), index[1]);
            group.clear();
        }
    }

    public static <E> void forEachGroup(
            Stream<E> stream,
            int groupSize,
            Consumer<Stream<E>> c
    ) {
        forEachGroup(stream, groupSize, (e, si) -> c.accept(e));
    }

    /**
     * 扩展路径字符串，将波浪符号扩展为用户目录，将环境变量名扩展为对应的值。
     *
     * @param path
     *         原始路径字符串，自动去掉首尾空格。
     * @return 扩展后的字符串，如果参数{@code path}是{@code null}或者只包含空白字符，则返回空字符串。
     */
    public static String expandPath(
            String path
    ) {
        path = trimToEmpty(path);
        if (path.isEmpty()) {
            return "";
        }

        if (path.startsWith("~")) {
            // 行首的波浪符应当被替换为用户目录。
            path = System.getProperty("user.home", "") + substring(path, 1);
        }

        final var expanded = replaceByRegex(ENV_VARIABLE_NAME, path, (index, matcher, lastMatched) -> {
            final var value = trimToEmpty(System.getenv(matcher.group(1)));
            return value.replace("\\", "\\\\").replace("$", "\\$");
        });

        try {
            // 尝试转化为唯一的绝对路径。
            return new File(expanded).getCanonicalPath();
        } catch (IOException ex) {
            return expanded;
        }
    }

    /**
     * 判断指定的参数值是否为空，如果是 {@code null} 则返回默认值。
     *
     * <p>等价于三元运算符，但是避免了对待判断值的两次求值。</p>
     * <code>
     * return v != null ? v : d;
     * </code>
     * <p>以上代码中，参数v被两次求值，通过此方法可以避免。</p>
     *
     * @param originalValue
     *         待判断的原始值。
     * @param defaultValue
     *         默认值。
     * @param <T>
     *         参数值的类型。
     * @return 如果参数originalValue 不是{@code null}则返回originalValue，否则返回defaultValue。
     */
    public static <T> T coalesce(
            T originalValue,
            T defaultValue
    ) {
        return originalValue != null ? originalValue : defaultValue;
    }

    /**
     * 判断指定的参数值是否为空或者零，如果是 {@code null} 或者 {@code 0} 则返回默认值。
     *
     * @param originalValue
     *         待判断的原始值。
     * @param defaultValue
     *         默认值。
     * @param <T>
     *         参数值的类型。
     * @return 如果参数originalValue 不是{@code null}且不是{@code 0}则返回originalValue，否则返回defaultValue。
     * @see #coalesce(Object, Object)
     */
    public static <T extends Number> T coalesceZero(
            T originalValue,
            T defaultValue
    ) {
        return originalValue != null && originalValue.intValue() != 0 ? originalValue : defaultValue;
    }

    /**
     * 判断指定的参数值是否为空或者零或者负数，如果是 {@code null} 或者 {@code 0} 或者负数 则返回默认值。
     *
     * @param originalValue
     *         待判断的原始值。
     * @param defaultValue
     *         默认值。
     * @param <T>
     *         参数值的类型。
     * @return 如果参数originalValue 不是{@code null} 且不是{@code 0}且不是负数则返回originalValue，否则返回defaultValue。
     * @see #coalesce(Object, Object)
     */
    public static <T extends Number> T coalesceNonPositive(
            T originalValue,
            T defaultValue
    ) {
        return originalValue != null && originalValue.intValue() > 0 ? originalValue : defaultValue;
    }

    /**
     * 扩展一个已有的{@link Set}, 将新的枚举元素插入到{@link Set}中。
     *
     * @param <T>
     *         枚举元素的类型。
     * @param clazz
     *         枚举元素的类型。
     * @param src
     *         已有的{@link Set}。
     * @param newElements
     *         需要插入的新枚举元素。
     * @return 包含已有枚举元素和新枚举元素的集合。
     * @throws java.lang.NullPointerException
     *         如果参数{@code clazz}是{@code null}。
     */
    @SafeVarargs
    public static <T extends Enum<T>> Set<T> extendEnumSet(
            Class<T> clazz,
            Set<T> src,
            T... newElements
    ) {
        rejectIfNull(clazz, "clazz");

        final var ret = src != null && !src.isEmpty() ? EnumSet.copyOf(src) : EnumSet.noneOf(clazz);

        if (newElements != null) {
            for (final var newElement : newElements) {
                if (newElement != null) {
                    ret.add(newElement);
                }
            }
        }

        return ret;
    }

    /**
     * 尝试将对象转化为指定的类型。
     *
     * @param <T>
     *         目标类型。
     * @param t
     *         待转化的对象。
     * @param clazz
     *         目标类型。
     * @return 转化的结果，如果参数{@code t}是{@code null}那么返回{@code null}，如果参数{@code t}可以被强制转化为{@code clazz}
     * ，那么返回{@code t}本身，否则也返回{@code null}。
     */
    public static <T> T tryCast(
            Object t,
            Class<T> clazz
    ) {
        rejectIfNull(clazz, "clazz");

        if (t != null) {
            if (clazz.isAssignableFrom(t.getClass())) {
                return clazz.cast(t);
            } else {
                return null;
            }
        } else {
            return null;
        }
    }

    /**
     * 将Map中的每个List值通过逗号连接起来并构造新的Map，原Map不受影响。
     * <p>示例：</p>
     * <pre>
     * { "a": [1, 2, 3], "b": [5] } -&gt; { "a": "1,2,3", "b": "5" }
     * </pre>
     *
     * @param src
     *         待处理的原Map。
     * @return 依次将每个值处理后的结果Map。
     */
    public static Map<String, String> flatJoinMap(
            Map<String, List<String>> src
    ) {
        rejectIfNull(src, "src");

        final var ret = new HashMap<String, String>(src.size());
        for (final var entry : src.entrySet()) {
            final var key = trimToEmpty(entry.getKey());
            final var values = entry.getValue();
            final var value = String.join(",", values);
            if (!key.isEmpty() && !value.isEmpty()) {
                ret.put(key, value);
            }
        }
        return ret;
    }

    /**
     * 获取指定异常对象的根异常的本地化错误消息。
     *
     * @param t
     *         指定的异常对象。
     * @return 根异常的本地化错误消息，如果参数{@code t == null}，那么返回{@code ""}。
     */
    public static String getRootCauseMessage(
            Throwable t
    ) {
        if (t != null) {
            while (t.getCause() != null)
                t = t.getCause();
            final var ret = t.getLocalizedMessage();
            return ret != null ? ret.trim() : t.toString();
        } else {
            return "";
        }
    }

    /**
     * 判断指定的掩码是否包含指定的枚举。
     *
     * @param mask
     *         掩码。
     * @param e
     *         枚举值。
     * @param <T>
     *         枚举类型。
     * @return 如果掩码包含了指定的枚举则返回{@code true}，否则返回{@code false}。
     * @throws IllegalArgumentException
     *         如果参数{@code e}是{@code null}。
     */
    public static <T extends Enum<T> & IntegerEnum> boolean isEnumSet(
            int mask,
            T e
    ) {
        rejectIfNull(e, "e");

        return (e.value() & mask) != 0L;
    }

    /**
     * 在掩码中置位指定的枚举，并返回新的掩码。
     *
     * @param mask
     *         原掩码。
     * @param enums
     *         需要置位的枚举。
     * @param <T>
     *         枚举类型。
     * @return 新的掩码。
     */
    @SafeVarargs
    public static <T extends Enum<T> & IntegerEnum> long setEnum(
            int mask,
            T... enums
    ) {
        if (enums == null) {
            return mask;
        }

        for (final var e : enums) {
            mask |= e.value();
        }

        return mask;
    }

    /**
     * 在掩码中复位指定的枚举，并返回新的掩码。
     *
     * @param mask
     *         原掩码。
     * @param enums
     *         需要复位的枚举。
     * @param <T>
     *         枚举类型。
     * @return 新的掩码。
     */
    @SafeVarargs
    public static <T extends Enum<T> & IntegerEnum> long unsetEnum(
            int mask,
            T... enums
    ) {
        if (enums == null) {
            return mask;
        }

        for (final var e : enums) {
            mask &= ~e.value();
        }

        return mask;
    }

    public static BigDecimal roundFloorPositive(
            BigDecimal v,
            BigDecimal divisor
    ) {
        rejectIfNegative(v, "v");
        rejectIfNegative(divisor, "divisor");

        if (v.signum() == 0) {
            return v;
        }

        divisor = divisor.setScale(0, RoundingMode.HALF_EVEN);
        if (divisor.intValue() == 0) {
            return v.setScale(0, RoundingMode.FLOOR);
        } else {
            return v.divide(divisor, 0, RoundingMode.FLOOR).multiply(divisor);
        }
    }

    public static BigDecimal roundCeilingPositive(
            BigDecimal v,
            BigDecimal divisor
    ) {
        rejectIfNegative(v, "v");
        rejectIfNegative(divisor, "divisor");

        if (v.signum() == 0) {
            return v;
        }

        divisor = divisor.setScale(0, RoundingMode.HALF_EVEN);
        if (divisor.intValue() == 0) {
            return v.setScale(0, RoundingMode.CEILING);
        } else {
            return v.divide(divisor, 0, RoundingMode.CEILING).multiply(divisor);
        }
    }

    /**
     * 将输入流的内容全部复制到输出流。
     * <p>复制完成后自动刷新输出流。</p>
     *
     * @param in
     *         包含待复制内容的输入流。
     * @param out
     *         输出流。
     * @return 成功复制的字节数。
     * @throws UncheckedIOException
     *         如果复制过程中出现IO错误。
     */
    public static int copyStream(
            InputStream in,
            OutputStream out
    ) {
        rejectIfNull(in, "in");
        rejectIfNull(out, "out");

        try {
            var byteCount = 0;
            var buffer = new byte[BUFFER_SIZE];
            var bytesRead = -1;
            while ((bytesRead = in.read(buffer)) != -1) {
                out.write(buffer, 0, bytesRead);
                byteCount += bytesRead;
            }
            out.flush();
            return byteCount;
        } catch (IOException ex) {
            throw new UncheckedIOException(ex);
        }
    }

    public static <T> Collector<T, ?, List<T>> toListWithDefault(
            Supplier<T> defaultValueSupplier,
            int minSize
    ) {
        rejectIfNull(defaultValueSupplier, "defaultValueSupplier");
        rejectIfNegative(minSize, "minSize");

        return new CollectorImpl<>(
                (Supplier<List<T>>) ArrayList::new,
                (list, item) -> list.add(item != null ? item : defaultValueSupplier.get()),
                (left, right) -> {
                    left.addAll(right);
                    return left;
                },
                l -> {
                    for (var i = l.size(); i < minSize; ++i) {
                        l.add(defaultValueSupplier.get());
                    }

                    return l;
                },
                EnumSet.noneOf(Collector.Characteristics.class)
        );
    }

    public static String[] readAllLines(
            InputStream in,
            Charset charset
    ) {
        rejectIfNull(in, "in");
        rejectIfNull(charset, "charset");

        try (final var r = new BufferedReader(new InputStreamReader(in, charset))) {
            return r.lines().toArray(String[]::new);
        } catch (IOException ex) {
            throw new UncheckedIOException(ex);
        }
    }

    public static String readString(
            InputStream in,
            Charset charset
    ) {
        return String.join("", readAllLines(in, charset));
    }

    /**
     * 查找列表中最小值的下标。
     * <p>跳过所有值为{@code null}的元素。如果参数{@code c}不包含不是{@code null}的元素，那么返回{@literal -1}。</p>
     *
     * @param c
     *         待查找的列表。
     * @param <T>
     *         列表中的元素类型。
     * @return 最小值的下标。
     * @throws IllegalArgumentException
     *         如果参数{@code c}是{@code null}。
     */
    public static <T extends Comparable<T>> int minIndex(
            List<T> c
    ) {
        rejectIfNull(c, "c");

        int result = -1;
        T minValue = null;

        for (var i = 0; i < c.size(); ++i) {
            final var value = c.get(i);
            if (value == null) {
                continue;
            }

            if (minValue == null) {
                minValue = value;
                result = i;
            } else if (value.compareTo(minValue) < 0) {
                minValue = value;
                result = i;
            }
        }

        return result;
    }

    /**
     * 查找列表中最大值的下标。
     * <p>跳过所有值为{@code null}的元素。如果参数{@code c}不包含不是{@code null}的元素，那么返回{@literal -1}。</p>
     *
     * @param c
     *         待查找的列表。
     * @param <T>
     *         列表中的元素类型。
     * @return 最大值的下标。
     * @throws IllegalArgumentException
     *         如果参数{@code c}是{@code null}。
     */
    public static <T extends Comparable<T>> int maxIndex(
            List<T> c
    ) {
        rejectIfNull(c, "c");

        int result = -1;
        T maxValue = null;

        for (var i = 0; i < c.size(); ++i) {
            final var value = c.get(i);
            if (value == null) {
                continue;
            }

            if (maxValue == null) {
                maxValue = value;
                result = i;
            } else if (value.compareTo(maxValue) > 0) {
                maxValue = value;
                result = i;
            }
        }

        return result;
    }

    /**
     * 替换列表中指定的元素。
     *
     * @param origin
     *         原始列表。
     * @param predication
     *         判断指定的元素是否需要替换。
     * @param op
     *         生成新的元素。
     * @param <E>
     *         元素类型。
     * @return 是否发生过替换。
     */
    public static <E> boolean replaceIf(
            List<E> origin,
            Predicate<E> predication,
            UnaryOperator<E> op
    ) {
        rejectIfNull(origin, "origin");
        rejectIfNull(predication, "predication");
        rejectIfNull(op, "op");

        var result = false;
        for (final var iter = origin.listIterator(); iter.hasNext(); ) {
            final var oldValue = iter.next();
            if (predication.test(oldValue)) {
                iter.remove();
                iter.add(op.apply(oldValue));
                result = true;
            }
        }

        return result;
    }

    public static void removeDirectories(
            Path directory
    )
            throws IOException {
        rejectIfNull(directory, "directory");
        rejectIf(() -> Files.isRegularFile(directory), "Argument \"directory\" must not be a regular file");

        if (!Files.exists(directory) || !Files.isDirectory(directory)) {
            return;
        }

        Files.walkFileTree(directory, new SimpleFileVisitor<>() {
            @Override
            public FileVisitResult visitFile(
                    Path file,
                    BasicFileAttributes attrs
            )
                    throws IOException {
                Files.delete(file);
                return FileVisitResult.CONTINUE;
            }

            public FileVisitResult postVisitDirectory(
                    Path dir,
                    IOException exc
            )
                    throws IOException {
                Files.delete(dir);
                return FileVisitResult.CONTINUE;
            }
        });
    }

    /**
     * 获取当前用户的HOME路径。
     * <p>通过java系统属性 {@literal user.home}来获取HOME路径。</p>
     *
     * @return 当前用户的HOME路径，该路径是绝对路径。
     */
    public static Path homePath() {
        final var home = System.getProperty("user.home", "");
        return Path.of(home).toAbsolutePath();
    }

    /**
     * 生成范围内的一组年月数据。
     *
     * @param start
     *         起始年月（包含）
     * @param end
     *         终止年月（不包含）
     * @return 位于 {@literal start} 和 {@literal end} 之间的一组年月数据。
     */
    public static Stream<YearMonth> range(
            YearMonth start,
            YearMonth end
    ) {
        rejectIfNull(start, "start");
        rejectIfNull(end, "end");

        if (start.compareTo(end) > 0) {
            return Stream.empty();
        }

        return Stream.iterate(start, item -> end.compareTo(item) > 0, item -> item.plusMonths(1));
    }

    public static <T extends Comparable<T>> T min(
            T t1,
            T t2
    ) {
        if (t1 == null) {
            return t2;
        } else if (t2 == null) {
            return t1;
        } else {
            return t1.compareTo(t2) <= 0 ? t1 : t2;
        }
    }

    public static <T extends Comparable<T>> T max(
            T t1,
            T t2
    ) {
        if (t1 == null) {
            return t2;
        } else if (t2 == null) {
            return t1;
        } else {
            return t1.compareTo(t2) >= 0 ? t1 : t2;
        }
    }

    public static Integer addInteger(
            Integer t1,
            Integer t2
    ) {
        if (t1 == null) {
            return t2;
        } else if (t2 == null) {
            return t1;
        } else {
            return t1 + t2;
        }
    }

    public static Long addLong(
            Long t1,
            Long t2
    ) {
        if (t1 == null) {
            return t2;
        } else if (t2 == null) {
            return t1;
        } else {
            return t1 + t2;
        }
    }

    public static int getLength(
            Collection<?> c
    ) {
        return c != null ? c.size() : 0;
    }

    public static int getLength(
            Map<?, ?> m
    ) {
        return m != null ? m.size() : 0;
    }

    public static int getLength(
            CharSequence cs
    ) {
        return cs != null ? cs.length() : 0;
    }

    public static int getLength(
            Object[] a
    ) {
        return a != null ? a.length : 0;
    }

    public static boolean isEmpty(
            Collection<?> c
    ) {
        return c == null || c.size() == 0;
    }

    public static boolean isEmpty(
            Map<?, ?> m
    ) {
        return m == null || m.size() == 0;
    }

    public static boolean isEmpty(
            CharSequence cs
    ) {
        return cs == null || cs.length() == 0;
    }

    public static boolean isEmpty(
            Object[] a
    ) {
        return a == null || a.length == 0;
    }

    public static <E> Stream<E> asStream(
            Collection<E> c
    ) {
        return c != null ? c.stream() : Stream.empty();
    }

    public static <K, V> Stream<Map.Entry<K, V>> asStream(
            Map<K, V> m
    ) {
        return m != null ? m.entrySet().stream() : Stream.empty();
    }

    public static <E> Stream<E> asStream(
            E[] a
    ) {
        return a != null ? stream(a) : Stream.empty();
    }

    public static IntStream asCharStream(
            CharSequence cs
    ) {
        return cs != null ? cs.chars() : IntStream.empty();
    }

    public static IntStream asCodePointStream(
            CharSequence cs
    ) {
        return cs != null ? cs.codePoints() : IntStream.empty();
    }

    /**
     * 包装一个自动关闭的输入流。
     * <p>当原始流所有元素都被读取后，即{@link InputStream#read()}方法返回{@literal -1}时，自动关闭原始流。</p>
     * <p>当包装流被关闭时，原始流也确保被关闭。</p>
     *
     * @param in
     *         原始输入流。
     * @return 包装输入流。
     * @throws NullPointerException
     *         如果参数 {@code in} 是 {@code null} 。
     */
    public static InputStream autoCloseStream(
            InputStream in
    ) {
        rejectIfNull(in, "in");

        return new InputStream() {
            private boolean closed = false;

            @Override
            public int read()
                    throws IOException {
                if (this.closed) {
                    return -1;
                }

                final var b = in.read();
                if (b < 0) {
                    LoggerFactory.getLogger(SysUtils.class).trace("Close inner stream since cannot read more bytes");
                    in.close();
                    this.closed = true;
                }

                return b;
            }

            @Override
            public void close()
                    throws IOException {
                super.close();

                LoggerFactory.getLogger(SysUtils.class).trace("Close inner stream by caller");
                if (!this.closed) {
                    in.close();
                    this.closed = true;
                }
            }
        };
    }

    public interface GroupConsumer<T> {
        void accept(
                Stream<T> groupStream,
                int startRowIndex
        );
    }

    record CollectorImpl<T, A, R>(Supplier<A> supplier, BiConsumer<A, T> accumulator,
                                  BinaryOperator<A> combiner, Function<A, R> finisher,
                                  Set<Characteristics> characteristics) implements Collector<T, A, R> {
    }
}
